/*
 * Copyright 2014 Jens K., Jalbum AB
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.package net.jalbum.jprint;
 */
package net.jalbum.areatagging;

import com.adobe.xmp.XMPException;
import com.adobe.xmp.XMPMeta;
import com.adobe.xmp.XMPMetaFactory;
import com.adobe.xmp.XMPSchemaRegistry;
import java.awt.Dimension;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import se.datadosen.jalbum.JAlbum;
import se.datadosen.jalbum.Msg;
import se.datadosen.jalbum.XmpManager;

/**
 * This class allows reading area tags from and writing them back to image meta
 * data.
 */
public class AreaTagHandler {

    private static boolean isSourceMWG = false;
    private static boolean isSourceMS = false;

    // Namespaces
    public static final String MWG_RS_NS = "http://www.metadataworkinggroup.com/schemas/regions/";
    public static final String STDIM_NS = "http://ns.adobe.com/xap/1.0/sType/Dimensions#";
    public static final String STAREA_NS = "http://ns.adobe.com/xmp/sType/Area#";
    public static final String MS_PHOTO_NS = "http://ns.microsoft.com/photo/1.2/";

    static {
        XMPSchemaRegistry registry = XMPMetaFactory.getSchemaRegistry();
        try {
            registry.registerNamespace(MWG_RS_NS, "mwg-rs");
            registry.registerNamespace(STDIM_NS, "stDim");
            registry.registerNamespace(STAREA_NS, "stArea");
        } catch (XMPException ex) {
            Logger.getLogger(AreaTagHandler.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public List<AreaTag> getAreaTags(XMPMeta meta, Dimension dim) throws XMPException, Exception {
        List<AreaTag> firstResultsMS;
        List<AreaTag> firstResultsMWG;
        List<AreaTag> firstResults;
        List<AreaTag> finalResults;

        firstResultsMWG = getMWGAreaTags(meta, dim);
        firstResultsMS = getMSAreaTags(meta, dim);
        firstResults = new ArrayList<AreaTag>();

        // check sources
        if (!firstResultsMWG.isEmpty()) {
            firstResults.addAll(firstResultsMWG);
            isSourceMWG = true;
        }

        if (!firstResultsMS.isEmpty()) {
            firstResults.addAll(firstResultsMS);
            isSourceMS = true;
        }

        // eliminate duplicates
        finalResults = new ArrayList<AreaTag>();
        boolean isDuplicate = false;

        for (AreaTag res1 : firstResults) {
            for (AreaTag res2 : finalResults) {
                isDuplicate = res1.equals(res2);

                if (isDuplicate) {
                    break;
                }
            }

            if (!isDuplicate) {
                finalResults.add(res1);
            }
        }

        return finalResults;
    }

    public boolean isSourceMWG() {
        return isSourceMWG;
    }

    public boolean isSourceMS() {
        return isSourceMS;
    }

    private List<AreaTag> getMWGAreaTags(XMPMeta meta, Dimension dim) throws XMPException {

        List<AreaTag> result = new ArrayList<AreaTag>();

        // create AreaTag objects for each already existing area tag
        boolean appleCheck = false;
        int tagCount = 0;

        // count the number of existing area tags
        try {
            tagCount = meta.countArrayItems(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:RegionList");
        } catch (XMPException ex) {
            // this can happen, in this case simply are no region information stored
            Logger.getLogger(AreaTagHandler.class.getName()).log(Level.FINE, null, ex);
        }

        if (tagCount == 0) {
            // abord if no region information stored
            return result;
        }

        for (int i = 1; i <= tagCount; i++) {
            // check if tag comes from apple and handle it if yes
            try {
                if (meta.getPropertyInteger(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:RegionList[" + i + "]/mwg-rs:Extensions/apple-fi:AngleInfoRoll") != null) {
                    appleCheck = true;
                }
            } catch (XMPException ex) {
                // this can happen, the tag is in this case not an apple area tag
                JAlbum.logger.finer(ex.toString());
            }

            // convert tag from apple if apple is source of the tag
            if (appleCheck) {
                result.add(convertAppleAreaTagToMWG(meta, dim, MWG_RS_NS, i));
                break;
            }

            // get area information
            Double x = meta.getPropertyDouble(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:RegionList[" + i + "]/mwg-rs:Area/stArea:x");
            Double y = meta.getPropertyDouble(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:RegionList[" + i + "]/mwg-rs:Area/stArea:y");

            // create AreaTag objects
            Double d = null;
            Double w = null;
            Double h = null;
            String name = null;
            String description = null;
            String type = null;

            // here exceptions can happen - if area tag is a circle or a point, than d, w and h do not have to exist
            try {
                d = meta.getPropertyDouble(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:RegionList[" + i + "]/mwg-rs:Area/stArea:d");
            } catch (XMPException ex) {
                JAlbum.logger.finer(ex.toString());
            }
            try {
                w = meta.getPropertyDouble(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:RegionList[" + i + "]/mwg-rs:Area/stArea:w");
            } catch (XMPException ex) {
                JAlbum.logger.finer(ex.toString());
            }
            try {
                h = meta.getPropertyDouble(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:RegionList[" + i + "]/mwg-rs:Area/stArea:h");
            } catch (XMPException ex) {
                JAlbum.logger.finer(ex.toString());
            }

            // get additional information, here non critical exceptions can happen, an area tag don't have to have these informations stored
            try {
                name = meta.getPropertyString(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:RegionList[" + i + "]/mwg-rs:Name");
            } catch (XMPException ex) {
                JAlbum.logger.finer(ex.toString());
            }
            try {
                description = meta.getPropertyString(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:RegionList[" + i + "]/mwg-rs:Description");
            } catch (XMPException ex) {
                JAlbum.logger.finer(ex.toString());
            }
            try {
                type = meta.getPropertyString(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:RegionList[" + i + "]/mwg-rs:Type");
            } catch (XMPException ex) {
                JAlbum.logger.finer(ex.toString());
            }

            // create ArrayTag object
            if (d == null && w == null && h == null) {
                // create area tag point
                result.add(new AreaTagPoint(name, description, type, x.floatValue(), y.floatValue()));
            } else if (d != null && w == null && h == null) {
                // create area tag circle
                result.add(new AreaTagCircle(name, description, type, x.floatValue(), y.floatValue(), d.floatValue()));
            } else if (d == null && w != null && h != null) {
                // create area tag rectangle
                result.add(new AreaTagRectangle(name, description, type, x.floatValue(), y.floatValue(), w.floatValue(), h.floatValue()));
            }
        }
        return result;
    }

    private AreaTag convertAppleAreaTagToMWG(XMPMeta meta, Dimension dim, String ns, int i) throws XMPException {
        Double iw = meta.getPropertyDouble(ns, "mwg-rs:Regions/mwg-rs:AppliedToDimensions/stDim:w");
        Double ih = meta.getPropertyDouble(ns, "mwg-rs:Regions/mwg-rs:AppliedToDimensions/stDim:h");
        Double angle = meta.getPropertyDouble(ns, "mwg-rs:Regions/mwg-rs:RegionList[" + i + "]/mwg-rs:Extensions/apple-fi:AngleInfoRoll");

        // get area information
        Double x = meta.getPropertyDouble(ns, "mwg-rs:Regions/mwg-rs:RegionList[" + i + "]/mwg-rs:Area/stArea:x");
        Double y = meta.getPropertyDouble(ns, "mwg-rs:Regions/mwg-rs:RegionList[" + i + "]/mwg-rs:Area/stArea:y");
        Double w = meta.getPropertyDouble(ns, "mwg-rs:Regions/mwg-rs:RegionList[" + i + "]/mwg-rs:Area/stArea:w");
        Double h = meta.getPropertyDouble(ns, "mwg-rs:Regions/mwg-rs:RegionList[" + i + "]/mwg-rs:Area/stArea:h");

        // create absolute values
        Double xOrigin = iw / 2;
        Double yOrigin = ih / 2;
        Double xOld = x * iw;
        Double yOld = y * ih;

        // calculate angle rad
        Double angleRad = angle / 180.0 * Math.PI;

        // calculate rotation
        Double xNew = (xOld - xOrigin) * Math.cos(angleRad) + (yOld - yOrigin) * Math.sin(angleRad) + xOrigin;
        Double yNew = (yOld - yOrigin) * Math.cos(angleRad) - (xOld - xOrigin) * Math.sin(angleRad) + yOrigin;

        // calculate normalized values
        xNew = 1.0 / iw * xNew;
        yNew = 1.0 / ih * yNew;

        // switch w/h if needed
        Double wNew, hNew;
        if (angle == 90.0 || angle == 270.0) {
            wNew = h;
            hNew = w;
        } else {
            wNew = w;
            hNew = h;
        }

        // create area tag rectangle
        return new AreaTagRectangle(null, null, null, xNew.floatValue(), yNew.floatValue(), wNew.floatValue(), hNew.floatValue());
    }

    private List<AreaTag> getMSAreaTags(XMPMeta meta, Dimension dim) throws XMPException {

        List<AreaTag> result = new ArrayList<AreaTag>();

        // create AreaTag objects for each already existing area tag     
        int tagCount = 0;

        // count the number of existing area tags
        try {
            tagCount = meta.countArrayItems(MS_PHOTO_NS, "MP:RegionInfo/MPRI:Regions");
        } catch (XMPException ex) {
            // this can happen, in this case simply are no region information stored
                JAlbum.logger.finer(ex.toString());
        }

        if (tagCount == 0) {
            // abord if no region information stored
            return result;
        }

        // create AreaTag objects
        Double x, y, w, h;
        String name = Msg.getString(this, "ui.newTag");
        String description = null;
        String type = "Face";

        // MS specific rect String
        String rect = null;

        for (int i = 1; i <= tagCount; i++) {

            // get area information
            rect = meta.getPropertyString(MS_PHOTO_NS, "MP:RegionInfo/MPRI:Regions[" + i + "]/MPReg:Rectangle");

            // get additional information, here non critical exceptions can happen, an area tag don't have to have those informations stored
            try {
                name = meta.getPropertyString(MS_PHOTO_NS, "MP:RegionInfo/MPRI:Regions[" + i + "]/MPReg:PersonDisplayName");
            } catch (XMPException ex) {
                JAlbum.logger.finer(ex.toString());
            }
            try {
                description = meta.getPropertyString(MS_PHOTO_NS, "MP:RegionInfo/MPRI:Regions[" + i + "]/MPReg:PersonEmailDigest");
            } catch (XMPException ex) {
                JAlbum.logger.finer(ex.toString());
            }

            // delete empty spaces
            String replaced = rect.replace(" ", "");

            // split rect String
            String[] rectCoords = replaced.split(",");
            x = Double.parseDouble(rectCoords[0]);
            y = Double.parseDouble(rectCoords[1]);
            w = Double.parseDouble(rectCoords[2]);
            h = Double.parseDouble(rectCoords[3]);

            // center origin (MS sets the origin to the upper left corner)
            double pixX = (dim.width * x) + (dim.width * w / 2);
            x = 1.0 / dim.width * pixX;
            double pixY = (dim.height * y) + (dim.height * h / 2);
            y = 1.0 / dim.height * pixY;

            result.add(new AreaTagRectangle(name, description, type, x.floatValue(), y.floatValue(), w.floatValue(), h.floatValue()));
        }
        return result;
    }

    void saveMWGAreaTags(XmpManager manager, Dimension dim, List<AreaTag> tags) throws XMPException, IOException {

        XMPMeta meta = manager.getXMPMeta();

        if (tags.isEmpty()) {
            meta.deleteProperty(MWG_RS_NS, "mwg-rs:Regions");
        } else {
            // clear old Region tags
            int items = meta.countArrayItems(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:RegionList");
            for (int i = items; i > 0; i--) {
                meta.deleteArrayItem(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:RegionList", i);
            }

            // store Dim information
            String widthS = "" + (int) dim.width;
            String heightS = "" + (int) dim.height;
            meta.setProperty(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:AppliedToDimensions/stDim:unit", "pixel");
            meta.setProperty(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:AppliedToDimensions/stDim:w", widthS);
            meta.setProperty(MWG_RS_NS, "mwg-rs:Regions/mwg-rs:AppliedToDimensions/stDim:h", heightS);

            // store Region information
            Integer item = 1;
            for (AreaTag tag : tags) {
                tag.storeMWGAreaTags(meta, item);
                item++;
            }
        }

        manager.save();
    }

    void saveMSAreaTags(XmpManager manager, Dimension dim, List<AreaTag> tags) throws XMPException, IOException {

        XMPMeta meta = manager.getXMPMeta();

        if (tags.isEmpty()) {
            meta.deleteProperty(MS_PHOTO_NS, "MP:RegionInfo");
        } else {
            // clear old Region tags
            int items = meta.countArrayItems(MS_PHOTO_NS, "MP:RegionInfo/MPRI:Regions");
            for (int i = items; i > 0; i--) {
                meta.deleteArrayItem(MS_PHOTO_NS, "MP:RegionInfo/MPRI:Regions", i);
            }

            // store Region information
            Integer item = 1;
            for (AreaTag tag : tags) {
                tag.storeMSAreaTags(meta, item, dim);
                item++;
            }
        }
        manager.save();
    }
}
